#pragma once

#include <map>
#include <memory>
#include <vector>
#include <algorithm>
#include <sstream>

namespace json {

    class Json {

    public:

        // Json枚举类型
        enum Type {
            Json_null = 0,
            Json_bool,
            Json_int,
            Json_double,
            Json_string,
            Json_array,
            Json_object
        };

        /**
          * @brief  默认构造函数
          * @note   构造出一个空类型的Json
          */
        Json();

        /**
          * @brief  不同类型的有参构造函数
          */
        Json(bool value);

        Json(int value);

        Json(double value);

        Json(const char *value);

        Json(const std::string &value);

        template<class T>
        Json(const std::vector<T> &value) {
            type_ = Json_array;
            value_ = std::shared_ptr<Value>(new Value, arrayDeleter);
            value_->array_ = new std::vector<Json>();
            for (const T &t: value) {
                value_->array_->push_back(t);
            }
        }

        Json(const Json &json) = default;

        /**
          * @brief  运算符重载
          * @Note   如果类型不对应将会抛出logic_error异常
          */
        operator bool();

        operator std::string();

        operator int();

        operator double();

        /**
          * @brief  获取当前Json节点的类型
          * @retval Json类型枚举
          */
        Type type() const { return type_; }

        /**
          * @brief  允许使用索引方式直接访问数组类型中的元素
          * @note   如果节点本身不是数组类型将会抛出logic_error异常，如果索引错误将会抛出out_of_range异常
          * @param  索引
          * @retval Json类型节点
          */
        Json &operator[](int index);

        /**
          * @brief  允许使用中括号运算符访问对象中指定键的值
          * @note   如果不是对象类型将抛出logic_error异常
          * @param  字符串
          * @retval Json类型节点
          */
        Json &operator[](const char *);

        Json &operator[](const std::string &);

        /**
          * @brief  获取数组类型的元素个数
          * @note   如果节点不是数组类型，将会抛出logic_error异常
          * @retval 数组元素个数(长度)
          */
        size_t length() const;

        /**
          * @brief  向数组中尾插一个Json类型的节点
          * @note   如果节点不是数组类型，将会抛出logic_error异常
          * @param  Json类型节点
          */
        void append(const Json &);

        /**
          * @brief  将节点转换为字符串类型
          * @retval 字符串
          */
        std::string toString() const;

        /**
          * @brief  将节点转换为基础数据类型
          * @note   如果类型错误将会抛出logic_error异常
          * @retval 基础数据类型
          */
        bool asBool() const;

        int asInt() const;

        double asDouble() const;

        std::string asString() const;


        /**
          * @brief  检查节点的类型
          * @retval 是否与要检查的类型一致
          */
        bool isNull() const { return type_ == Json_null; }

        bool isInt() const { return type_ == Json_int; }

        bool isDouble() const { return type_ == Json_double; }

        bool isBool() const { return type_ == Json_bool; }

        bool isString() const { return type_ == Json_string; }

        bool isObject() const { return type_ == Json_object; }

        bool isArray() const { return type_ == Json_array; }

        /**
          * @brief  检查数组类型的节点中是否包含指定索引的元素
          * @param  要检查的索引
          * @retval 是否存在该元素
          */
        bool has(int) const;

        /**
          * @brief  检查对象类型的节点中是否存在指定的键值
          * @param  要检查的键
          * @retval 是否存在指定的键
          */
        bool has(const char *) const;

        bool has(const std::string &) const;

        /**
          * @brief  移除数组类型节点中的指定索引的节点元素
          * @note   如果不存在或类型错误不会抛出异常，不会对自身节点造成任何影响
          * @param  要移除的指定节点的索引
          */
        void remove(int);

        /**
          * @brief  移除对象类型节点中的指定键的节点
          * @note   如果不存在或类型错误不会抛出异常，不会对自身节点造成任何影响
          * @param  要移除的键
          */
        void remove(const char *);

        void remove(const std::string &);

        /**
          * @brief  静态函数，将字符串处理为Json节点
          * @note   处理失败将抛出logic_error异常
          * @param  要处理的Json格式字符串
          * @retval Json类型的节点
          */
        static Json parseJson(const std::string &);

        static Json parseJson(const char *);

    private:

        union Value {
            bool bool_;
            int int_;
            double double_;
            std::string *string_;
            std::vector<Json> *array_;
            std::map<std::string, Json> *object_;
        };

        static void stringDeleter(Value *value) { delete value->string_; }

        static void arrayDeleter(Value *value) { delete value->array_; }

        static void objectDeleter(Value *value) { delete value->object_; }

        static Json parseValue(std::stringstream &);

        static Json parseString(std::stringstream &);

        static Json parseObject(std::stringstream &);

        static Json parseArray(std::stringstream &);

        static Json parseNumber(std::stringstream &);

        static Json parseBool(std::stringstream &);

        Type type_;

        std::shared_ptr<Value> value_;

    };

    Json::Json() {
        type_ = Json_null;
    }

    Json::Json(bool value) : type_(Json_bool) {
        value_ = std::make_shared<Value>();
        value_->bool_ = value;
    }

    Json::Json(int value) : type_(Json_int) {
        value_ = std::make_shared<Value>();
        value_->int_ = value;
    }

    Json::Json(double value) : type_(Json_double) {
        value_ = std::make_shared<Value>();
        value_->double_ = value;
    }

    Json::Json(const char *value) : type_(Json_string) {
        value_ = std::shared_ptr<Value>(new Value, stringDeleter);
        value_->string_ = new std::string(value);
    }

    Json::Json(const std::string &value) : type_(Json_string) {
        value_ = std::shared_ptr<Value>(new Value, stringDeleter);
        value_->string_ = new std::string(value);
    }

    Json::operator bool() {
        if (type_ != Json_bool) {
            throw std::logic_error("type error , not bool type");
        }
        return value_->bool_;
    }

    Json::operator std::string() {
        if (type_ != Json_string) {
            throw std::logic_error("type error , not string type");
        }
        return *value_->string_;
    }

    Json::operator int() {
        if (type_ != Json_int) {
            throw std::logic_error("type error , not int type");
        }
        return value_->int_;
    }

    Json::operator double() {
        if (type_ != Json_double) {
            throw std::logic_error("type error , not double type");
        }
        return value_->double_;
    }

    Json &Json::operator[](int index) {
        if (value_ == nullptr) {
            throw std::out_of_range("index out of Json array range");
        }
        if (type_ != Json_array) {
            throw std::logic_error("type error , not array type");
        }
        if (index > value_->array_->size()) {
            throw std::out_of_range("index out of Json array range");
        }
        return value_->array_->at(index);
    }

    Json &Json::operator[](const char *key) {
        if (value_ == nullptr) {
            type_ = Json_object;
            value_ = std::shared_ptr<Value>(new Value, objectDeleter);
            value_->object_ = new std::map<std::string, Json>();
        }
        if (type_ != Json_object) {
            throw std::logic_error("type error , not object type");
        }
        return (*(value_->object_))[key];
    }

    Json &Json::operator[](const std::string &key) {
        return (*this)[key.c_str()];
    }

    size_t Json::length() const {
        if (type_ != Json_array) {
            throw std::logic_error("type error , not array type");
        }
        return value_->array_->size();
    }

    void Json::append(const Json &json) {
        if (value_ == nullptr) {
            type_ = Json_array;
            value_ = std::shared_ptr<Value>(new Value, arrayDeleter);
            value_->array_ = new std::vector<Json>();
        } else if (type_ != Json_array) {
            throw std::logic_error("type error , not array type");
        }
        value_->array_->push_back(json);
    }

    std::string Json::toString() const {
        std::stringstream ss;
        switch (type_) {
            case Json_null:
                ss << "null";
                break;
            case Json_int:
                ss << value_->int_;
                break;
            case Json_string:
                ss << '\"' << *(value_->string_) << '\"';
                break;
            case Json_double:
                ss << value_->double_;
                break;
            case Json_bool: {
                if (value_->bool_) {
                    ss << "true";
                } else {
                    ss << "false";
                }
                break;
            }
            case Json_array: {
                ss << '[';
                for (auto i = value_->array_->begin(); i != value_->array_->end(); ++i) {
                    if (i != value_->array_->begin()) {
                        ss << ',';
                    }
                    ss << i->toString();
                }
                ss << ']';
                break;
            }
            case Json_object: {
                ss << '{';
                for (auto i = value_->object_->begin(); i != value_->object_->end(); ++i) {
                    if (i != value_->object_->begin()) {
                        ss << ',';
                    }
                    ss << '\"' << i->first << "\":" << i->second.toString();
                }
                ss << '}';
                break;
            }
            default:
                break;
        }
        return ss.str();
    }

    Json Json::parseValue(std::stringstream &ss) {
        while (ss.peek() != -1) {
            if (ss.peek() == '\t' || ss.peek() == '\r' || ss.peek() == ' ' || ss.peek() == '\n') {
                ss.get();
            } else if (ss.peek() == '\"') {
                return parseString(ss);
            } else if (ss.peek() == '{') {
                return parseObject(ss);
            } else if (ss.peek() == '[') {
                return parseArray(ss);
            } else if (ss.peek() == 'f' || ss.peek() == 't') {
                return parseBool(ss);
            } else {
                return parseNumber(ss);
            }
        }
        return Json();
    }

    Json Json::parseJson(const std::string &json) {
        std::stringstream ss;
        ss.str(json);
        return parseValue(ss);
    }

    Json Json::parseJson(const char *json) {
        std::stringstream ss;
        ss.str(json);
        return parseValue(ss);
    }

    Json Json::parseBool(std::stringstream &ss) {
        std::streampos pos = ss.tellg();
        if (ss.peek() == 't') {
            ss.seekg(pos + std::streampos(4));
            return {true};
        } else if (ss.peek() == 'f') {
            ss.seekg(pos + std::streampos(5));
            return {false};
        }
        throw std::logic_error("bad type conversion to bool");
    }

    Json Json::parseString(std::stringstream &ss) {
        ss.get();
        std::string ret;
        while (ss.peek() != '"') {
            ret.push_back((char) ss.get());
        }
        ss.get();
        return {ret};
    }

    Json Json::parseObject(std::stringstream &ss) {
        ss.get();
        Json json;
        while (ss.peek() != '}') {
            std::string key = parseValue(ss).toString().erase(0, 1);
            key.pop_back();
            while (ss.peek() == ' ' || ss.peek() == ':') {
                ss.get();
            }
            json[key] = parseValue(ss);
            while (ss.peek() != '}' &&
                   (ss.peek() == ' ' || ss.peek() == '\t' || ss.peek() == '\n' || ss.peek() == '\r' || ss.peek() == ',')) {
                ss.get();
            }
        }
        return json;
    }

    Json Json::parseArray(std::stringstream &ss) {
        ss.get();
        Json json;
        while (ss.peek() != ']') {
            json.append(parseValue(ss));
            while (ss.peek() != ']' && (ss.peek() == ' ' || ss.peek() == '\t' || ss.peek() == '\n' || ss.peek() == ',')) {
                ss.get();
            }
        }
        ss.get();
        return json;
    }

    Json Json::parseNumber(std::stringstream &ss) {
        std::string s;
        while (isdigit(ss.peek()) || ss.peek() == 'e' || ss.peek() == '-' || ss.peek() == '+' || ss.peek() == '.') {
            s.push_back((char) ss.get());
        }
        if (count(s.begin(), s.end(), '.') || count(s.begin(), s.end(), 'e')) {
            return {stof(s)};
        } else {
            return {stoi(s)};
        }
    }

    bool Json::asBool() const {
        if (type_ != Json_bool) {
            throw std::logic_error("type error , not bool type");
        }
        return value_->bool_;
    }

    int Json::asInt() const {
        if (type_ != Json_int) {
            throw std::logic_error("type error , not int type");
        }
        return value_->int_;
    }

    double Json::asDouble() const {
        if (type_ != Json_double) {
            throw std::logic_error("type error , not double type");
        }
        return value_->double_;
    }

    std::string Json::asString() const {
        if (type_ != Json_string) {
            throw std::logic_error("type error , not string type");
        }
        return *(value_->string_);
    }

    bool Json::has(int index) const {
        if (type_ != Json_array || index < 0) {
            return false;
        }
        return index < value_->array_->size();
    }

    bool Json::has(const char *key) const {
        if (type_ != Json_object) {
            return false;
        }
        return value_->object_->count(key);
    }

    bool Json::has(const std::string &key) const {
        if (type_ != Json_object) {
            return false;
        }
        return value_->object_->count(key);
    }

    void Json::remove(int index) {
        if (!has(index)) {
            return;
        }
        value_->array_->erase(value_->array_->begin() + index);
    }

    void Json::remove(const char *key) {
        remove(std::string(key));
    }

    void Json::remove(const std::string &key) {
        if (!has(key)) {
            return;
        }
        value_->object_->erase(key);
    }

}